www.gusucode.com > Piwik 网站流量统计系统 v2.9.1PHP源码程序 > Piwik 网站流量统计系统 v2.9.1/How to install Piwik.html/piwik/core/Plugin/Report.php

    <?php
/**
 * Piwik - free/libre analytics platform
 *
 * @link http://piwik.org
 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 *
 */
namespace Piwik\Plugin;

use Piwik\API\Proxy;
use Piwik\API\Request;
use Piwik\Cache\LanguageAwareStaticCache;
use Piwik\Columns\Dimension;
use Piwik\DataTable;
use Piwik\Menu\MenuReporting;
use Piwik\Metrics;
use Piwik\Piwik;
use Piwik\Plugin\Manager as PluginManager;
use Piwik\Plugins\CoreVisualizations\Visualizations\HtmlTable;
use Piwik\Translate;
use Piwik\WidgetsList;
use Piwik\ViewDataTable\Factory as ViewDataTableFactory;
use Exception;

/**
 * Defines a new report. This class contains all information a report defines except the corresponding API method which
 * needs to be defined in the 'API.php'. You can define the name of the report, a documentation, the supported metrics,
 * how the report should be displayed, which features the report has (eg search) and much more.
 *
 * You can create a new report using the console command `./console generate:report`. The generated report will guide
 * you through the creation of a report.
 *
 * @since 2.5.0
 * @api
 */
class Report
{
    /**
     * The sub-namespace name in a plugin where Report components are stored.
     */
    const COMPONENT_SUBNAMESPACE = 'Reports';

    /**
     * When added to the menu, a given report eg 'getCampaigns'
     * will be routed as &action=menuGetCampaigns
     */
    const PREFIX_ACTION_IN_MENU = 'menu';

    /**
     * The name of the module which is supposed to be equal to the name of the plugin. The module is detected
     * automatically.
     * @var string
     */
    protected $module;

    /**
     * The name of the action. The action is detected automatically depending on the file name. A corresponding action
     * should exist in the API as well.
     * @var string
     */
    protected $action;

    /**
     * The translated name of the report. The name will be used for instance in the mobile app or if another report
     * defines this report as a related report.
     * @var string
     * @api
     */
    protected $name;

    /**
     * A translated documentation which explains the report.
     * @var string
     */
    protected $documentation;

    /**
     * The translation key of the category the report belongs to.
     * @var string
     * @api
     */
    protected $category;

    /**
     * The translation key of the widget title. If a widget title is set, the platform will automatically configure/add
     * a widget for this report. Alternatively, this behavior can be overwritten in {@link configureWidget()}.
     * @var string
     * @api
     */
    protected $widgetTitle;

    /**
     * Optional widget params that will be appended to the widget URL if a {@link $widgetTitle} is set.
     * @var array
     * @api
     */
    protected $widgetParams = array();

    /**
     * The translation key of the menu title. If a menu title is set, the platform will automatically add a menu item
     * to the reporting menu. Alternatively, this behavior can be overwritten in {@link configureReportingMenu()}.
     * @var string
     * @api
     */
    protected $menuTitle;

    /**
     * An array of supported metrics. Eg `array('nb_visits', 'nb_actions', ...)`. Defaults to the platform default
     * metrics see {@link Metrics::getDefaultProcessedMetrics()}.
     * @var array
     * @api
     */
    protected $metrics = array('nb_visits', 'nb_uniq_visitors', 'nb_actions', 'nb_users');
    // for a little performance improvement we avoid having to call Metrics::getDefaultMetrics for each report

    /**
     * The processed metrics this report supports, eg `avg_time_on_site` or `nb_actions_per_visit`. Defaults to the
     * platform default processed metrics, see {@link Metrics::getDefaultProcessedMetrics()}. Set it to boolean `false`
     * if your report does not support any processed metrics at all. Otherwise an array of metric names.
     * Eg `array('avg_time_on_site', 'nb_actions_per_visit', ...)`
     * @var array|false
     * @api
     */
    protected $processedMetrics = array('nb_actions_per_visit', 'avg_time_on_site', 'bounce_rate', 'conversion_rate');
    // for a little performance improvement we avoid having to call Metrics::getDefaultProcessedMetrics for each report

    /**
     * Set this property to true in case your report supports goal metrics. In this case, the goal metrics will be
     * automatically added to the report metadata and the report will be displayed in the Goals UI.
     * @var bool
     * @api
     */
    protected $hasGoalMetrics = false;

    /**
     * Set it to boolean `true` if your report always returns a constant count of rows, for instance always 24 rows
     * for 1-24 hours.
     * @var bool
     * @api
     */
    protected $constantRowsCount = false;

    /**
     * Set it to boolean `true` if this report is a subtable report and won't be used as a standalone report.
     * @var bool
     * @api
     */
    protected $isSubtableReport = false;

    /**
     * Some reports may require additonal URL parameters that need to be sent when a report is requested. For instance
     * a "goal" report might need a "goalId": `array('idgoal' => 5)`.
     * @var null|array
     * @api
     */
    protected $parameters = null;

    /**
     * An instance of a dimension if the report has one. You can create a new dimension using the Piwik console CLI tool
     * if needed.
     * @var \Piwik\Columns\Dimension
     */
    protected $dimension;

    /**
     * The name of the API action to load a subtable if supported. The action has to be of the same module. For instance
     * a report "getKeywords" might support a subtable "getSearchEngines" which shows how often a keyword was searched
     * via a specific search engine.
     * @var string
     * @api
     */
    protected $actionToLoadSubTables = '';

    /**
     * The order of the report. Depending on the order the report gets a different position in the list of widgets,
     * the menu and the mobile app.
     * @var int
     * @api
     */
    protected $order = 1;

    /**
     * @var array
     * @ignore
     */
    public static $orderOfReports = array(
        'General_MultiSitesSummary',
        'VisitsSummary_VisitsSummary',
        'Goals_Ecommerce',
        'General_Actions',
        'Events_Events',
        'Actions_SubmenuSitesearch',
        'Referrers_Referrers',
        'Goals_Goals',
        'General_Visitors',
        'DevicesDetection_DevicesDetection',
        'UserSettings_VisitorSettings',
    );

    /**
     * The constructur initializes the module, action and the default metrics. If you want to overwrite any of those
     * values or if you want to do any work during initializing overwrite the method {@link init()}.
     * @ignore
     */
    final public function __construct()
    {
        $classname    = get_class($this);
        $parts        = explode('\\', $classname);

        if (5 === count($parts)) {
            $this->module = $parts[2];
            $this->action = lcfirst($parts[4]);
        }

        $this->init();
    }

    /**
     * Here you can do any instance initialization and overwrite any default values. You should avoid doing time
     * consuming initialization here and if possible delay as long as possible. An instance of this report will be
     * created in most page requests.
     * @api
     */
    protected function init()
    {
    }

    /**
     * Defines whether a report is enabled or not. For instance some reports might not be available to every user or
     * might depend on a setting (such as Ecommerce) of a site. In such a case you can perform any checks and then
     * return `true` or `false`. If your report is only available to users having super user access you can do the
     * following: `return Piwik::hasUserSuperUserAccess();`
     * @return bool
     * @api
     */
    public function isEnabled()
    {
        return true;
    }

    /**
     * This method checks whether the report is available, see {@isEnabled()}. If not, it triggers an exception
     * containing a message that will be displayed to the user. You can overwrite this message in case you want to
     * customize the error message. Eg.
     * ```
     if (!$this->isEnabled()) {
         throw new Exception('Setting XYZ is not enabled or the user has not enough permission');
     }
     * ```
     * @throws \Exception
     * @api
     */
    public function checkIsEnabled()
    {
        if (!$this->isEnabled()) {
            throw new Exception(Piwik::translate('General_ExceptionReportNotEnabled'));
        }
    }

    /**
     * Returns the id of the default visualization for this report. Eg 'table' or 'pie'. Defaults to the HTML table.
     * @return string
     * @api
     */
    public function getDefaultTypeViewDataTable()
    {
        return HtmlTable::ID;
    }

    /**
     * Here you can configure how your report should be displayed and which capabilities your report has. For instance
     * whether your report supports a "search" or not. EG `$view->config->show_search = false`. You can also change the
     * default request config. For instance you can change how many rows are displayed by default:
     * `$view->requestConfig->filter_limit = 10;`. See {@link ViewDataTable} for more information.
     * @param ViewDataTable $view
     * @api
     */
    public function configureView(ViewDataTable $view)
    {

    }

    /**
     * Renders a report depending on the configured ViewDataTable see {@link configureView()} and
     * {@link getDefaultTypeViewDataTable()}. If you want to customize the render process or just render any custom view
     * you can overwrite this method.
     *
     * @return string
     * @throws \Exception In case the given API action does not exist yet.
     * @api
     */
    public function render()
    {
        $apiProxy = Proxy::getInstance();

        if (!$apiProxy->isExistingApiAction($this->module, $this->action)) {
            throw new Exception("Invalid action name '$this->action' for '$this->module' plugin.");
        }

        $apiAction = $apiProxy->buildApiActionName($this->module, $this->action);

        $view      = ViewDataTableFactory::build(null, $apiAction, $this->module . '.' . $this->action);
        $rendered  = $view->render();

        return $rendered;
    }

    /**
     * By default a widget will be configured for this report if a {@link $widgetTitle} is set. If you want to customize
     * the way the widget is added or modify any other behavior you can overwrite this method.
     * @param WidgetsList $widget
     * @api
     */
    public function configureWidget(WidgetsList $widget)
    {
        if ($this->widgetTitle) {
            $params = array();
            if (!empty($this->widgetParams) && is_array($this->widgetParams)) {
                $params = $this->widgetParams;
            }
            $widget->add($this->category, $this->widgetTitle, $this->module, $this->action, $params);
        }
    }

    /**
     * By default a menu item will be added to the reporting menu if a {@link $menuTitle} is set. If you want to
     * customize the way the item is added or modify any other behavior you can overwrite this method. For instance
     * in case you need to add additional url properties beside module and action which are added by default.
     * @param \Piwik\Menu\MenuReporting $menu
     * @api
     */
    public function configureReportingMenu(MenuReporting $menu)
    {
        if ($this->menuTitle) {
            $action = $this->getMenuControllerAction();
            if ($this->isEnabled()) {
                $menu->addItem($this->category,
                               $this->menuTitle,
                               array('module' => $this->module, 'action' => $action),
                               $this->order);
            }
        }
    }

    /**
     * Returns an array of supported metrics and their corresponding translations. Eg `array('nb_visits' => 'Visits')`.
     * By default the given {@link $metrics} are used and their corresponding translations are looked up automatically.
     * If a metric is not translated, you should add the default metric translation for this metric using
     * the {@hook Metrics.getDefaultMetricTranslations} event. If you want to overwrite any default metric translation
     * you should overwrite this method, call this parent method to get all default translations and overwrite any
     * custom metric translations.
     * @return array
     * @api
     */
    public function getMetrics()
    {
        return $this->getMetricTranslations($this->metrics);
    }

    /**
     * Returns an array of supported processed metrics and their corresponding translations. Eg
     * `array('nb_visits' => 'Visits')`. By default the given {@link $processedMetrics} are used and their
     * corresponding translations are looked up automatically. If a metric is not translated, you should add the
     * default metric translation for this metric using the {@hook Metrics.getDefaultMetricTranslations} event. If you
     * want to overwrite any default metric translation you should overwrite this method, call this parent method to
     * get all default translations and overwrite any custom metric translations.
     * @return array
     * @api
     */
    public function getProcessedMetrics()
    {
        if (!is_array($this->processedMetrics)) {
            return $this->processedMetrics;
        }

        return $this->getMetricTranslations($this->processedMetrics);
    }

    /**
     * Returns an array of metric documentations and their corresponding translations. Eg
     * `array('nb_visits' => 'If a visitor comes to your website for the first time or if he visits a page more than 30 minutes after...')`.
     * By default the given {@link $metrics} are used and their corresponding translations are looked up automatically.
     * If there is a metric documentation not found, you should add the default metric documentation translation for
     * this metric using the {@hook Metrics.getDefaultMetricDocumentationTranslations} event. If you want to overwrite
     * any default metric translation you should overwrite this method, call this parent method to get all default
     * translations and overwrite any custom metric translations.
     * @return array
     * @api
     */
    protected function getMetricsDocumentation()
    {
        $translations  = Metrics::getDefaultMetricsDocumentation();
        $documentation = array();

        foreach ($this->metrics as $metric) {
            if (!empty($translations[$metric])) {
                $documentation[$metric] = $translations[$metric];
            }
        }

        return $documentation;
    }

    /**
     * @return bool
     * @ignore
     */
    public function hasGoalMetrics()
    {
        return $this->hasGoalMetrics;
    }

    /**
     * If the report is enabled the report metadata for this report will be built and added to the list of available
     * reports. Overwrite this method and leave it empty in case you do not want your report to be added to the report
     * metadata. In this case your report won't be visible for instance in the mobile app and scheduled reports
     * generator. We recommend to change this behavior only if you are familiar with the Piwik core. `$infos` contains
     * the current requested date, period and site.
     * @param $availableReports
     * @param $infos
     * @api
     */
    public function configureReportMetadata(&$availableReports, $infos)
    {
        if (!$this->isEnabled()) {
            return;
        }

        $report = $this->buildReportMetadata();

        if (!empty($report)) {
            $availableReports[] = $report;
        }
    }

    /**
     * Builts the report metadata for this report. Can be useful in case you want to change the behavior of
     * {@link configureReportMetadata()}.
     * @return array
     * @ignore
     */
    protected function buildReportMetadata()
    {
        $report = array(
            'category' => $this->getCategory(),
            'name'     => $this->getName(),
            'module'   => $this->getModule(),
            'action'   => $this->getAction()
        );

        if (null !== $this->parameters) {
            $report['parameters'] = $this->parameters;
        }

        if (!empty($this->dimension)) {
            $report['dimension'] = $this->dimension->getName();
        }

        if (!empty($this->documentation)) {
            $report['documentation'] = $this->documentation;
        }

        if (true === $this->isSubtableReport) {
            $report['isSubtableReport'] = $this->isSubtableReport;
        }

        $report['metrics']              = $this->getMetrics();
        $report['metricsDocumentation'] = $this->getMetricsDocumentation();
        $report['processedMetrics']     = $this->getProcessedMetrics();

        if (!empty($this->actionToLoadSubTables)) {
            $report['actionToLoadSubTables'] = $this->actionToLoadSubTables;
        }

        if (true === $this->constantRowsCount) {
            $report['constantRowsCount'] = $this->constantRowsCount;
        }

        $report['order'] = $this->order;

        return $report;
    }

    /**
     * Get the list of related reports if there are any. They will be displayed for instance below a report as a
     * recommended related report.
     *
     * @return Report[]
     * @api
     */
    public function getRelatedReports()
    {
        return array();
    }

    /**
     * Gets the translated widget title if one is defined.
     * @return string
     * @ignore
     */
    public function getWidgetTitle()
    {
        if ($this->widgetTitle) {
            return Piwik::translate($this->widgetTitle);
        }
    }

    /**
     * Get the name of the report
     * @return string
     * @ignore
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Get the name of the module.
     * @return string
     * @ignore
     */
    public function getModule()
    {
        return $this->module;
    }

    /**
     * Get the name of the action.
     * @return string
     * @ignore
     */
    public function getAction()
    {
        return $this->action;
    }

    /**
     * Get the translated name of the category the report belongs to.
     * @return string
     * @ignore
     */
    public function getCategory()
    {
        return Piwik::translate($this->category);
    }

    /**
     * @return \Piwik\Columns\Dimension
     * @ignore
     */
    public function getDimension()
    {
        return $this->dimension;
    }

    /**
     * Returns the order of the report
     * @return int
     * @ignore
     */
    public function getOrder()
    {
        return $this->order;
    }

    /**
     * Get the menu title if one is defined.
     * @return string
     * @ignore
     */
    public function getMenuTitle()
    {
        return $this->menuTitle;
    }

    /**
     * Get the action to load sub tables if one is defined.
     * @return string
     * @ignore
     */
    public function getActionToLoadSubTables()
    {
        return $this->actionToLoadSubTables;
    }

    /**
     * Returns the Dimension instance of this report's subtable report.
     *
     * @return Dimension|null The subtable report's dimension or null if there is subtable report or
     *                        no dimension for the subtable report.
     * @api
     */
    public function getSubtableDimension()
    {
        if (empty($this->actionToLoadSubTables)) {
            return null;
        }

        list($subtableReportModule, $subtableReportAction) = $this->getSubtableApiMethod();

        $subtableReport = self::factory($subtableReportModule, $subtableReportAction);
        if (empty($subtableReport)) {
            return null;
        }

        return $subtableReport->getDimension();
    }

    /**
     * Returns true if the report is for another report's subtable, false if otherwise.
     *
     * @return bool
     */
    public function isSubtableReport()
    {
        return $this->isSubtableReport;
    }

    /**
     * Fetches the report represented by this instance.
     *
     * @param array $paramOverride Query parameter overrides.
     * @return DataTable
     * @api
     */
    public function fetch($paramOverride = array())
    {
        return Request::processRequest($this->module . '.' . $this->action, $paramOverride);
    }

    /**
     * Fetches a subtable for the report represented by this instance.
     *
     * @param int $idSubtable The subtable ID.
     * @param array $paramOverride Query parameter overrides.
     * @return DataTable
     * @api
     */
    public function fetchSubtable($idSubtable, $paramOverride = array())
    {
        $paramOverride = array('idSubtable' => $idSubtable) + $paramOverride;

        list($module, $action) = $this->getSubtableApiMethod();
        return Request::processRequest($module . '.' . $action, $paramOverride);
    }

    /**
     * Get an instance of a specific report belonging to the given module and having the given action.
     * @param  string $module
     * @param  string $action
     * @return null|\Piwik\Plugin\Report
     * @api
     */
    public static function factory($module, $action)
    {
        return ComponentFactory::factory($module, ucfirst($action), __CLASS__);
    }

    /**
     * Returns a list of all available reports. Even not enabled reports will be returned. They will be already sorted
     * depending on the order and category of the report.
     * @return \Piwik\Plugin\Report[]
     * @api
     */
    public static function getAllReports()
    {
        $reports = PluginManager::getInstance()->findMultipleComponents('Reports', '\\Piwik\\Plugin\\Report');
        $cache   = new LanguageAwareStaticCache('Reports' . implode('', $reports));

        if (!$cache->has()) {
            $instances = array();

            foreach ($reports as $report) {
                $instances[] = new $report();
            }

            usort($instances, array('self', 'sort'));

            $cache->set($instances);
        }

        return $cache->get();
    }

    /**
     * API metadata are sorted by category/name,
     * with a little tweak to replicate the standard Piwik category ordering
     *
     * @param Report $a
     * @param Report $b
     * @return int
     */
    private static function sort($a, $b)
    {
        return ($category = strcmp(array_search($a->category, self::$orderOfReports), array_search($b->category, self::$orderOfReports))) == 0
            ? ($a->order < $b->order ? -1 : 1)
            : $category;
    }

    private function getMetricTranslations($metricsToTranslate)
    {
        $translations = Metrics::getDefaultMetricTranslations();
        $metrics = array();

        foreach ($metricsToTranslate as $metric) {
            if (!empty($translations[$metric])) {
                $metrics[$metric] = $translations[$metric];
            } else {
                $metrics[$metric] = $metric;
            }
        }

        return $metrics;
    }

    private function getMenuControllerAction()
    {
        return self::PREFIX_ACTION_IN_MENU . ucfirst($this->action);
    }

    private function getSubtableApiMethod()
    {
        if (strpos($this->actionToLoadSubTables, '.') !== false) {
            return explode('.', $this->actionToLoadSubTables);
        } else {
            return array($this->module, $this->actionToLoadSubTables);
        }
    }

    /**
     * Finds a top level report that provides stats for a specific Dimension.
     *
     * @param Dimension $dimension The dimension whose report we're looking for.
     * @return Report|null The
     * @api
     */
    public static function getForDimension(Dimension $dimension)
    {
        return ComponentFactory::getComponentif (__CLASS__, $dimension->getModule(), function (Report $report) use ($dimension) {
            return !$report->isSubtableReport()
                && $report->getDimension()
                && $report->getDimension()->getId() == $dimension->getId();
        });
    }
}